// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// intrinsic-gen parses the <tint>/src/intrinsics.def file, then scans the
// project directory for '<file>.tmpl' files, to produce '<file>' source code
// files.
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/gen"
	"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/parser"
	"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/resolver"
	"dawn.googlesource.com/tint/tools/src/fileutils"
	"dawn.googlesource.com/tint/tools/src/glob"
)

const defProjectRelPath = "src/intrinsics.def"

func main() {
	if err := run(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func showUsage() {
	fmt.Println(`
intrinsic-gen generates the intrinsic table for the Tint compiler

intrinsic-gen parses the <tint>/src/intrinsics.def file, then scans the project
directory for '<file>.tmpl' files, to produce '<file>' source code files.

usage:
  intrinsic-gen

optional flags:`)
	flag.PrintDefaults()
	fmt.Println(``)
	os.Exit(1)
}

func run() error {
	// Load the intrinsics definition file
	projectRoot := fileutils.ProjectRoot()
	defPath := filepath.Join(projectRoot, defProjectRelPath)

	defSource, err := ioutil.ReadFile(defPath)
	if err != nil {
		return err
	}

	// Parse the definition file to produce an AST
	ast, err := parser.Parse(string(defSource), defProjectRelPath)
	if err != nil {
		return err
	}

	// Resolve the AST to produce the semantic info
	sem, err := resolver.Resolve(ast)
	if err != nil {
		return err
	}

	// Recursively find all the template files in the <tint>/src directory
	files, err := glob.Scan(projectRoot, glob.MustParseConfig(`{
		"paths": [{"include": [
			"src/**.tmpl",
			"test/**.tmpl"
		]}]
	}`))
	if err != nil {
		return err
	}

	// For each template file...
	for _, relTmplPath := range files {
		// Make tmplPath absolute
		tmplPath := filepath.Join(projectRoot, relTmplPath)

		// Read the template file
		tmpl, err := ioutil.ReadFile(tmplPath)
		if err != nil {
			return fmt.Errorf("failed to open '%v': %w", tmplPath, err)
		}

		// Create or update the file at relpath if the file content has changed
		// relpath is a path relative to the template
		writeFile := func(relpath, body string) error {
			// Write the common file header
			sb := strings.Builder{}
			sb.WriteString(fmt.Sprintf(header, filepath.ToSlash(relTmplPath), filepath.ToSlash(defProjectRelPath)))
			sb.WriteString(body)
			content := sb.String()
			abspath := filepath.Join(filepath.Dir(tmplPath), relpath)
			return writeFileIfChanged(abspath, content)
		}

		// Write the content generated using the template and semantic info
		sb := strings.Builder{}
		if err := gen.Generate(sem, string(tmpl), &sb, writeFile); err != nil {
			return fmt.Errorf("while processing '%v': %w", tmplPath, err)
		}

		if body := sb.String(); body != "" {
			_, tmplFileName := filepath.Split(tmplPath)
			outFileName := strings.TrimSuffix(tmplFileName, ".tmpl")
			if err := writeFile(outFileName, body); err != nil {
				return err
			}
		}
	}

	return nil
}

// writes content to path if the file has changed
func writeFileIfChanged(path, content string) error {
	existing, err := ioutil.ReadFile(path)
	if err == nil && string(existing) == content {
		return nil // Not changed
	}
	if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
		return fmt.Errorf("failed to create directory for '%v': %w", path, err)
	}
	if err := ioutil.WriteFile(path, []byte(content), 0666); err != nil {
		return fmt.Errorf("failed to write file '%v': %w", path, err)
	}
	return nil
}

const header = `// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

////////////////////////////////////////////////////////////////////////////////
// File generated by tools/intrinsic-gen
// using the template:
//   %v
// and the intrinsic defintion file:
//   %v
//
// Do not modify this file directly
////////////////////////////////////////////////////////////////////////////////

`
